package smtp_package;

import general.OSDetection;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Calendar;

/**
 * @author Cristovao Rufino
 * 
 * <p>Servidor de SMTP simplificado para o projeto 
 * de Redes de Computadores 1</p>
 * Classe de acordo com o Gambi Design Pattern Mega Zord
 * Para mais informacoes visite:
 * <a href="http://desciclo.pedia.ws/wiki/Gambi_Design_Patterns#Mega_Zord">
 * Gambi Design Patterns</a> 
 */

public class smtpServer implements Runnable {

	private static final String[] POSSIBLE_COMMANDS = {
		"HELO", 		//Tanto HELO ou EHLO servem para comecar a transmissao
		"MAIL FROM",	//Especifica o e-mail remoto
		"RCPT TO", 		//Especifica o e-mail de destino
		"DATA",			//Comeca a pegar os dados para o e-mail
		".",			//Termina a transmissao dos dados
		"VRFY",			//Verifica se existe um e-mail no servidor
		"QUIT",			//Finaliza a conexao com o servidor
		"HELP",			//Exibe a ajuda... nao sei ainda o que vou mostrar
		"SHOW INFO"		//Exibe tudo sobre o e-mail atual
	};
	
	//Indices dos comandos de SMTP
	private static final int HELO = 0;
	private static final int MAIL_FROM = 1;
	private static final int RCPT_TO = 2;
	private static final int DATA = 3;
	private static final int END_MESSAGE = 4;
	private static final int VRFY = 5;
	private static final int QUIT = 6;
	private static final int HELP = 7;
	private static final int SHOW_INFO = 8;

	
	//Definicao de variaveis locais
	private PrintWriter buffW; //Buffer de saida
	private BufferedReader buffR;
	private InputStream in;
	private OutputStream out;
	private Socket socket;
	
	private String remoteHost = "";
	private String mailFrom = "";
	private String rcptTo = "";
	private String messageData = "";
	
	private boolean recievingEmail = false;
			
	/**
	 * @author Cristovao Rufino
	 * @param PrintWriter buffW
	 * Inicia um servidor SMTP para receber uma mensagem
	 */
	
	public smtpServer(Socket socket){
		this.socket = socket;

			System.out.println(	
				"Conexao iniciada com: " + socket.getRemoteSocketAddress().toString() + " na porta "
				+ socket.getLocalPort() );
		
			//Cria um stream de saida e entrada de dados
			try {
				
				InputStream in = new DataInputStream(socket.getInputStream());
				OutputStream out = new DataOutputStream(socket.getOutputStream());
	
				//Cria um BufferedReader para a entrada e um PrintWriter para
				//a saida
				this.buffR =
					new BufferedReader(new InputStreamReader(in));
				this.buffW =
					new PrintWriter(new OutputStreamWriter(out));

				buffW.write("Servidor ACF Versao 1.0 a seu dispor\n");
				buffW.flush();
			
			} catch (Exception e) {
				e.printStackTrace();
			}

	}
	
	/**
	 * @author Cristovao Rufino
	 * 
	 * Codigo responsavel pelo multi threading do servidor
	 */
	
	public void run() {

		String data; int ans;
		try {
			
			while( (data = buffR.readLine()) != null ){
				ans = this.parseSmtpCommand(data);
				if(ans == 0)
					break;
			}
		
			System.out.println("Conexao terminada!");
			//Tenta fechar todos os sockets e servers 

			buffW.close();
			buffR.close();
			out.close();
			in.close();
			socket.close();

		} catch (Exception e) {
			/*
			 * Deixa eu pensar... deu problema no fechamento das conexoes
			 * sabe o que eu vou fazer?? NADA :D
			 */
		}


	}

	/**
	 * @author Cristovao Rufino
	 * @param String command
	 * <br><p>Executa um comando de SMTP</p>
	 */
	 
	public int parseSmtpCommand(String command){
		
		String extraData; //Dados extra para o comando
		String commandUCase = command.toUpperCase();
		int commandIndex; //Index do comando na lista de comandos validos
		
		for(commandIndex = 0; commandIndex < POSSIBLE_COMMANDS.length; commandIndex++)
			//Verifica se um comando dado e valido.
			if(commandUCase.indexOf(POSSIBLE_COMMANDS[commandIndex]) != -1)
				break;
			
			if( (commandIndex == END_MESSAGE) && !commandUCase.equals("."))
				commandIndex = POSSIBLE_COMMANDS.length;
		
		switch (commandIndex) {
//-------------------------------------------------------------------------			
		case QUIT:
			messagePost("\nXauzinho ;)\n\n");
			return 0;
//-------------------------------------------------------------------------			
		case HELO:
			if(command.indexOf(' ') != -1){
				extraData = command.substring(command.indexOf(' ') + 1).trim();
				this.remoteHost = extraData;
				messagePost("Ola " + extraData + "! Seja bem vindo ;)\n");
			} else {
				messagePost("ERR - Voce deve especificar um host de origem!\n");
			}
			
			return 1;
//-------------------------------------------------------------------------				
		case MAIL_FROM:
			if( (command.indexOf('<') != -1) && (command.indexOf('>') != -1) ){
				extraData = command.substring(command.indexOf('<') + 1, command.indexOf('>')).trim();
				this.mailFrom = extraData;
				messagePost(extraData + ": Ok\n");
			} else {
				messagePost("ERR - Voce deve especificar um remetente entre '<' e '>'!\n");
			}
				
			return 1;
//-------------------------------------------------------------------------
			case RCPT_TO:
				if( (command.indexOf('<') != -1) && (command.indexOf('>') != -1) ){
					extraData = command.substring(command.indexOf('<') + 1, command.indexOf('>'));
					//deve ser escrito um codigo de verificacao para ver se
					//o email dado existe no servidor!
					this.rcptTo = extraData;
					messagePost(extraData + ": Ok\n");
				} else {
					messagePost("ERR - Voce deve especificar um destinatario entre '<' e '>'!\n");
				}
				
				return 1;
//-------------------------------------------------------------------------
			case DATA:
				if(!this.mailFrom.equals("") && !this.rcptTo.equals("")){
					messagePost("Termine os dados com <CR><LF>.<CR><LF>\n");
					this.recievingEmail = true;
				} else {
					messagePost("ERR - Voce deve especificar o remetente e o destinatario antes de escrever o e-mail!\n");
				}
					
				return 1;
//-------------------------------------------------------------------------
			case END_MESSAGE:
				if (this.recievingEmail == false){
					messagePost("ERR - Comando desconhecido: " + command + "\n");
				} else {
					messagePost("Mensagem concluida e salva na caixa de " + this.rcptTo + "!\n");
					this.recievingEmail = false;

					writeEmail(); //Escreve um arquivo com o e-mail da pessoa

				}
					
			return 1;
//-------------------------------------------------------------------------
			case VRFY:
				if( (command.indexOf('<') == -1) && (command.indexOf('>') == -1) ){
					messagePost("ERR - Voce deve especificar um endereco para ser buscado!\n");
				} else {
					//Deve ser escrito o codigo para verificar a existencia
					//de um email no servidor
					extraData = command.substring(command.indexOf('<') + 1, command.indexOf('>'));
					messagePost("Oops... " + extraData + " nao pode ser encontrado pois AINDA NAO FOI ESCRITO O CODIGO!!\n");
				}

				return 1;
//-------------------------------------------------------------------------
			case HELP:
				//Escrever possivel codigo para exibir ajuda... se der :P
				messagePost("\nTa precisando de ajuda?? Vai pro Google, oras!\n\n");
				return 1;
//-------------------------------------------------------------------------
			case SHOW_INFO:
				messagePost(
				"\nInformacoes do e-mail:\n" +
				"Host remoto: " + this.remoteHost + "\n" +
				"Remetente: " + this.mailFrom + "\n" + 
				"Destinatario: " + this.rcptTo +
				"\n\n"
				);
				return 1;
//-------------------------------------------------------------------------
		default: //E utilizado para receber entrada de dados para o DATA

			if( this.recievingEmail == true ){
				this.messageData = this.messageData + command + "\n";
			} else {
				messagePost("ERR - Comando desconhecido: " + command + "\n");
			}
			
			return 1;
			
		} //switch
			
	} //parseSmtpCommand

//-----------------------------------------------------------------------	
	/**
	 * @author Cristovao Rufino
	 * @param nenhum
	 * @return Nome da caixa de correio do e-mail
	 */
	
	private String getMailBox(){
		return this.rcptTo.substring( 0, this.rcptTo.indexOf("@") );
	}
	
	/**
	 * @author Cristovao Rufino
	 * @param String message
	 * @return none
	 * <br>Envia uma mensagem de volta para o cliente
	 */
	
	private void messagePost(String message){
		this.buffW.write(message);
		this.buffW.flush();
	}

	/**
	 * @author Cristovao Rufino
	 * @return none
	 * <br>Escreve o e-mail recebido em um arquivo .eml
	 */
	
	private void writeEmail(){
		
		//Modificar o codigo para que escreva no local certo
		
		try {
			Calendar myCal = Calendar.getInstance();
			//Um numero aleatorio para que não haja sobregravação da mensagem no HD
			long rndNum = (long)(myCal.getTimeInMillis() * Math.random());
			FileOutputStream saida = null;
			
			try {
				File location = new File(".");
				String myLocation = location.getAbsolutePath().substring(0, location.getAbsolutePath().length() - 1);
			
				saida = new FileOutputStream(myLocation + "mailbox" +
					OSDetection.SLASH + getMailBox() + 
					OSDetection.SLASH + "mail-" + rndNum + ".eml");

				
				String message = this.messageData; 
				
				saida.write(message.getBytes("UTF-8"));
				saida.flush();
				
			}
			
			catch (Exception e){ //Quando nao existe a caixa de e-mail

				System.out.println("Caixa de e-mail nao encontrada! Enviando mensagem de conserto");

				if(verify(this.mailFrom)){
					
					File checkout = new File(".");
					String myPath = checkout.getAbsolutePath().substring(0, checkout.getAbsolutePath().length() - 1);

					myPath = myPath + "mailbox" + OSDetection.SLASH +
						this.mailFrom.substring(0, this.mailFrom.indexOf("@")) + OSDetection.SLASH;
					
					myCal = Calendar.getInstance();
					String fileName = "mail-" + (long)(myCal.getTimeInMillis()*Math.random()) + ".eml";
					
					FileOutputStream file = new FileOutputStream(myPath + fileName);
					
					String errorMessage = "From: sistemaderecebimento\nTo: " + this.mailFrom + "\n" +
							"Subject: E-mail nao encontrado\n\nA segunte mensagem e para dizer que o e-mail " +
							this.rcptTo + " nao foi encontrado no sistema!";
					
					file.write(errorMessage.getBytes());
					file.flush();
					
					file.close();
					
				} 
				
			} //Catch

			if(saida != null)
				try {
					saida.close();
				}
				catch (Exception e){ } //Erro ao fechar arquivo...
			
		}
		catch (Exception e){ //Erro de arquivo não encontrado
			System.out.println(e.getMessage());
		}
		
	}
	
	/**
	 * @author Cristovao Rufino
	 * @param String inetAdd - Endereco de e-mail para verificar
	 * @return Boolean - true se existe e false se nao existe
	 */
	
	private boolean verify(String inetAdd){
		File checkout;
		String myPath;
		boolean status = false;
		
		try {
			checkout = new File(".");
			myPath = checkout.getAbsolutePath().substring(0, checkout.getAbsolutePath().length() - 1);
			myPath = myPath + "mailbox" + OSDetection.SLASH +
				this.mailFrom.substring(0, this.mailFrom.indexOf("@")) + OSDetection.SLASH;

			checkout = new File(myPath + inetAdd.substring(0, inetAdd.indexOf("@")));
			if (checkout != null)
				status = true;
			
		}
		catch (Exception e){
			System.out.println(e.getMessage());
		}
	
		return status;
	}
	
}
